﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.Serialization;
using System.Xml.Serialization;

using GolfSigma.Inventory.Extension;

namespace GolfSigma.Inventory.Model
{
    #region Enumerations tied directed to TeeTime(s)

    public enum FeeEnum : int
    {
        UnKnown = 0,
        Green = 1,
        Cart = 2,
        Booking = 3,
        Tax = 4,
        RackRate = 5
    }

    public enum TeeTimeAttributeEnum : int
    {
        Holes18 = 0,
        Holes9 = 5,
        Holes27 = 10,
        Holes36 = 15,
        ResidentRestriction = 20,
        ResidentOnly = 25,
        GPSIncluded = 30,
        MembersOnly = 35,
        MemberDiscount = 40,
        PlayerCardDiscount = 45,
        PlayerCardOnly = 50,
        PracticeBalls = 55,
        SeniorDiscount = 60,
        SeniorOnly = 65,
        Twilight = 70,
        TaxIncluded = 75,
        Walking = 80,
        CartIncluded = 85,
        CaddieIncluded = 90,
        PullCartIncluded = 95,
        Replay = 100,
        Aerification = 105,
        MilitaryDiscount = 110,
        ShotgunStart = 115,
        CourseAlert = 120,
        FreeReplay = 125,
        OverSeeding = 130,
        BestRateGuarantee = 135,
        CartPathOnly = 140,
        PrePaidOnline = 145
    }

    [Flags]
    public enum PlayerEnum : int
    {
        None = 0x0,
        Single = 0x1,
        Twosome = 0x2,
        Threesome = 0x4,
        Foursome = 0x8,
        All = Single | Twosome | Threesome | Foursome
    }

    /// <summary>
    /// Currencies and their official names
    /// </summary>
    public enum CurrencyEnum
    {
        /// <summary>
        /// United States dollar
        /// </summary>
        USD = 0,

        /// <summary>
        /// British Pound
        /// </summary>
        GBP = 5,

        /// <summary>
        /// Canadian dollar
        /// </summary>
        CAD = 10,

        /// <summary>
        /// Euro
        /// </summary>
        EUR = 15,

        /// <summary>
        /// Australian dollar
        /// </summary>
        AUD = 20,

        /// <summary>
        /// Japanese yen
        /// </summary>
        JPY = 25,

        /// <summary>
        /// Swiss Franc
        /// </summary>
        CHF = 30,

        /// <summary>
        /// Hong Kong Dollar
        /// </summary>
        HKD = 35,

        /// <summary>
        /// Mexican Peso
        /// </summary>
        MXN = 40
    }

    #endregion

    [XmlRoot("TeeTime")]
    public class TeeTime : IXmlSerializable
    {
        #region Ids

        /// <summary>
        /// Id of the tee-time. Id will be dropped on insert(s)
        /// </summary>
        public UInt64 TeeTimeId { get; set; }

        /// <summary>
        /// Website for the specific tee-time. 
        /// </summary>
        public UInt32 WebSiteId { get; set; }

        /// <summary>
        /// Course for the specific tee-time
        /// </summary>
        public UInt64 CourseId { get; set; }

        #endregion

        #region Time and Players

        /// <summary>
        /// Date and Time of the tee-time.
        /// </summary>
        public DateTime Time { get; set; }

        /// <summary>
        /// Number of players available for the current tee time. Use bitwise operators to determine the number
        /// of players available.
        /// </summary>
        public PlayerEnum Players { get; set; }

        /// <summary>
        /// Miscellaneous data. Providers can store information used to regenerate lookups to their own tee-time ids etc.
        /// </summary>
        public string MiscData { get; set; }

        #endregion

        #region Fees

        /// <summary>
        /// Total searchType per player. Sum of GreenFee, CartFee, Tax and Booking Fee
        /// </summary>
        public float TotalPricePerPlayer { get; set; }

        /// <summary>
        /// Percentage of interval off the original green fee
        /// </summary>
        public double? Savings { get; set; }

        /// <summary>
        /// Detailed breakdown of fees for this tee-time
        /// </summary>
        public Dictionary<FeeEnum, float> Fees { get; set; }

        /// <summary>
        /// Currency of the fees and total price per player
        /// </summary>
        public CurrencyEnum Currency { get; set; }

        #endregion

        #region Dates

        /// <summary>
        /// DateTime of when this tee time was created. (UTC) (Item will be dropped on any insert or update.)
        /// </summary>
        public DateTime Created { get; set; }

        /// <summary>
        /// DateTime of when this tee-time was last updated. (UTC) (Item will be dropped on any insert or update.)
        /// </summary>
        public DateTime? Updated { get; set; }

        /// <summary>
        /// DateTime of when this tee-time was deleted. (UTC) (Item will be dropped on any insert or update.)
        /// </summary>
        public DateTime? Deleted { get; set; }

        #endregion

        #region Attributes

        /// <summary>
        /// Attributes for the given tee times. List of integers (comma separated when serialized) representing
        /// all attributes that apply for the given tee time
        /// </summary>
        public List<TeeTimeAttributeEnum> Attributes { get; set; }

        private int? _NumberOfHoles;
        /// <summary>
        /// The number of holes for this tee-time
        /// ** ASSUME 18 HOLES!! **
        /// </summary>
        public int NumberOfHoles
        {
            get
            {
                if (_NumberOfHoles.HasValue)
                    return _NumberOfHoles.Value;

                if (this.Attributes == null || this.Attributes.Count < 1)
                {
                    _NumberOfHoles = 18;
                }
                else
                {
                    if (this.Attributes.Contains(TeeTimeAttributeEnum.Holes9))
                        _NumberOfHoles = 9;
                    else if (this.Attributes.Contains(TeeTimeAttributeEnum.Holes27))
                        _NumberOfHoles = 27;
                    else if (this.Attributes.Contains(TeeTimeAttributeEnum.Holes36))
                        _NumberOfHoles = 36;
                    else
                        _NumberOfHoles = 18;
                }

                // Return the number of holes
                return _NumberOfHoles.Value;
            }
        }

        #endregion

        #region IXmlSerializable Members

        public System.Xml.Schema.XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader)
        {
            // Check to see if we were provided an id
            string teeTimeId = reader.GetAttribute("id");
            if (!string.IsNullOrEmpty(teeTimeId))
            {
                UInt64 teeTimeIdParsed;
                if (UInt64.TryParse(teeTimeId, out teeTimeIdParsed))
                    TeeTimeId = teeTimeIdParsed;
                else
                    throw new FormatException();
            }

            bool breakRead = false;
            while (breakRead == false)
            {
                reader.Read();
                string localName = reader.LocalName;

                string elementAsString = string.Empty;
                elementAsString = reader.ReadString();

                switch (localName)
                {
                    case "WebSiteId":
                        UInt32? webSiteIdParsed = StringExtension.ToUInt32(elementAsString);
                        if (!webSiteIdParsed.HasValue)
                            throw new FormatException();
                        else
                            WebSiteId = webSiteIdParsed.Value;
                        break;

                    case "CourseId":
                        UInt64? courseIdParsed = StringExtension.ToUInt64(elementAsString);
                        if (!courseIdParsed.HasValue)
                            throw new FormatException();
                        else
                            CourseId = courseIdParsed.Value;
                        break;

                    case "Players":
                        int playersParsed = 0;
                        if (!int.TryParse(elementAsString, out playersParsed))
                            throw new FormatException();
                        else
                            Players = (PlayerEnum)playersParsed;
                        if (Players == PlayerEnum.None)
                            throw new FormatException();
                        break;

                    case "Time":
                        DateTime timeParsed;
                        if (!DateTime.TryParseExact(
                            elementAsString,
                            Extension.StringExtension.DateTimeFormatShort,
                            System.Globalization.CultureInfo.InvariantCulture,
                            System.Globalization.DateTimeStyles.AssumeLocal | System.Globalization.DateTimeStyles.AllowWhiteSpaces,
                            out timeParsed))
                            throw new FormatException();
                        else
                            Time = timeParsed;
                        break;

                    case "Created":
                        DateTime createdParsed;
                        if (!DateTime.TryParseExact(
                            elementAsString,
                            Extension.StringExtension.DateTimeFormatUTC,
                            System.Globalization.CultureInfo.InvariantCulture,
                            System.Globalization.DateTimeStyles.AssumeUniversal | System.Globalization.DateTimeStyles.AllowWhiteSpaces,
                            out createdParsed))
                            throw new FormatException();
                        else
                            Created = createdParsed;
                        break;

                    case "Deleted":
                        DateTime deletedParsed;
                        if (!DateTime.TryParseExact(
                            elementAsString,
                            Extension.StringExtension.DateTimeFormatUTC,
                            System.Globalization.CultureInfo.InvariantCulture,
                            System.Globalization.DateTimeStyles.AssumeUniversal | System.Globalization.DateTimeStyles.AllowWhiteSpaces,
                            out deletedParsed))
                            throw new FormatException();
                        else
                            Deleted = deletedParsed;
                        break;

                    case "Price":
                        float priceParsed;
                        if (float.TryParse(elementAsString, out priceParsed))
                            TotalPricePerPlayer = priceParsed;
                        else
                            // Failed (anything lower than -1 is a failure)
                            throw new FormatException();
                        if (TotalPricePerPlayer < -1 || TotalPricePerPlayer > 1000)
                            throw new FormatException();
                        break;

                    case "Currency":
                        try
                        {
                            CurrencyEnum currencyParsed = (CurrencyEnum)Enum.Parse(
                                typeof(CurrencyEnum),
                                elementAsString,
                                true);

                            Currency = currencyParsed;
                        }
                        catch
                        {
                            throw new FormatException();
                        }
                        break;

                    case "Savings":
                        Double savingsParsed;
                        if (double.TryParse(elementAsString, out savingsParsed))
                            Savings = savingsParsed;
                        else
                            throw new FormatException();
                        break;

                    case "Fees":
                        // Create a new list
                        if (Fees == null)
                            Fees = new Dictionary<FeeEnum, float>();

                        while (reader.IsStartElement("Fee"))
                        {
                            string feeType = reader.GetAttribute("type");
                            string feeValue = reader.GetAttribute("value");

                            // Skip if we don't have a fee AND value
                            if (string.IsNullOrEmpty(feeType) || string.IsNullOrEmpty(feeValue))
                            {
                                reader.Read();
                                continue;
                            }

                            FeeEnum feeTypeParsed = FeeEnum.UnKnown;
                            try
                            {
                                // Attempt to convert the feeType into an enum
                                feeTypeParsed = (FeeEnum)Enum.Parse(typeof(FeeEnum), feeType);
                            }
                            catch
                            {
                                throw new FormatException();
                            }

                            // Attempt to parse the value
                            float feeValueParsed;
                            if (float.TryParse(feeValue, out feeValueParsed))
                                Fees.Add(feeTypeParsed, feeValueParsed);
                            else
                                throw new FormatException();

                            // Done reading this element
                            reader.Read();
                        }
                        break;

                    case "Attributes":
                        // Make sure we have attributes
                        if (!string.IsNullOrEmpty(elementAsString))
                        {
                            // Create a new list if it doesn't exist
                            if (Attributes == null)
                                Attributes = new List<TeeTimeAttributeEnum>();

                            // Loop through each attribute and add it to the attribute list!
                            foreach (var attr in elementAsString.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
                            {
                                try
                                {
                                    // Attempt to parse the attribute
                                    TeeTimeAttributeEnum attrParsed =
                                        (TeeTimeAttributeEnum)Enum.Parse(typeof(TeeTimeAttributeEnum), attr, true);

                                    if (StringExtension.IsNumber(attrParsed.ToString(), false))
                                    {
                                        throw new FormatException();
                                    }

                                    // Add to the list
                                    Attributes.Add(attrParsed);
                                }
                                catch
                                {
                                    throw new FormatException();
                                }
                            }
                        }
                        break;

                    case "Misc":
                        if (!string.IsNullOrEmpty(elementAsString))
                            MiscData = elementAsString;
                        break;

                    case "TeeTime":
                        // End reading this element
                        reader.ReadEndElement();
                        breakRead = true;
                        break;

                    // Do nothing on any other attribute
                    default: break;
                }
            }
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Post Parameters

        public Dictionary<string, string> GetPostParamters()
        {
            return GetPostParamters(default(int?));
        }

        public Dictionary<string, string> GetPostParamters(int? index)
        {
            string prefix = "teetime.";

            // Create our result
            Dictionary<string, string> result = new Dictionary<string, string>();

            result.Add(StringExtension.Prefix("websiteid", prefix, index), WebSiteId.ToString());
            result.Add(StringExtension.Prefix("courseid", prefix, index), CourseId.ToString());
            result.Add(StringExtension.Prefix("time", prefix, index), Time.ToString(Extension.StringExtension.DateTimeFormatShort));
            result.Add(StringExtension.Prefix("players", prefix, index), ((int)Players).ToString());
            result.Add(StringExtension.Prefix("currency", prefix, index), Currency.ToString());

            // Add our savings
            if (Savings.HasValue)
                result.Add(StringExtension.Prefix("savings", prefix, index), Savings.Value.ToString());

            // Convert our attributes to filters
            if (Attributes != null && Attributes.Count > 0)
            {
                StringBuilder sbAttributes = new StringBuilder();
                foreach (var item in Attributes)
                    sbAttributes.AppendFormat("{0},", ((int)item).ToString());

                // Add without the trailing comma
                result.Add(StringExtension.Prefix("attributes", prefix, index), sbAttributes.ToString(0, sbAttributes.Length - 1));
            }

            // Convert our fees
            if (Fees != null && Fees.Count > 0)
            {
                string feePrefix = "fee.";
                int i = 1;
                foreach (var item in Fees)
                {
                    string keyPrefix = StringExtension.Prefix(StringExtension.Prefix("key", feePrefix, i), prefix, index);
                    string valuePrefix = StringExtension.Prefix(StringExtension.Prefix("value", feePrefix, i), prefix, index);

                    result.Add(keyPrefix, item.Key.ToString());
                    result.Add(valuePrefix, item.Value.ToString());
                    i++;
                }
            }

            if (!string.IsNullOrEmpty(MiscData))
                result.Add(StringExtension.Prefix("miscdata", prefix, index), MiscData);

            // Return the params
            return result;
        }

        #endregion

        public TeeTime() { }
        public TeeTime(TeeTime input) : this()
        {
            if (input == null)
                return;

            this.Attributes = input.Attributes;
            this.CourseId = input.CourseId;
            this.Created = input.Created;
            this.Currency = input.Currency;
            this.Deleted = input.Deleted;
            this.Fees = input.Fees;
            this.MiscData = input.MiscData;
            this.Players = input.Players;
            this.Savings = input.Savings;
            this.TeeTimeId = input.TeeTimeId;
            this.Time = input.Time;
            this.TotalPricePerPlayer = input.TotalPricePerPlayer;
            this.Updated = input.Updated;
            this.WebSiteId = input.WebSiteId;
        }
    }
}
